import { Component } from 'react'
import { connect } from 'react-redux'
import { message, Spin } from 'antd'
import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import PropTypes from 'prop-types'

import './index.css'
import ProcessEditorContext from './context'
import Toolbar from './panels/Toolbar'
import Palette from './panels/Palette'
import Property from './panels/Property'
import DesignPanel from './panels/DesignPanel'
import { findGroup, addGroup } from './utils'
import FLOWABLE from './FLOWABLE'
import EditorManager from './EditorManager'
import { loadDesignDepsAction, loadJQueryAction } from "../../store/actions/flowableAction"
import { queryEditorJson, queryEditorStencil, queryOryxPlugins, loadLang } from '../../apis/processModel'

class ProcessDesigner extends Component {

  static propTypes = {
    modelId: PropTypes.string.isRequired
  }

  state = {
    loading: false,
    editorManager: null,
    stencilItemGroups: [],
    quickMenuItems: [],
    morphRoles: [],
    containmentRules: [],

    previousSelectedShape: null,
    selectedShapes: [],
    selectedShape: null
  }

  async componentDidMount() {
    this.setState({
      loading: true
    })
    try {
      await this.doLoadDeps()
      await this.initEditor()
    } catch (error) {
      throw(error)
    }
  }

  componentWillUnmount() {
    const { editorManager } = this.state
    if(editorManager){
      editorManager.handleEvents({
        type: ORYX.CONFIG.EVENT_SAVED
      });
    }
  }

  doLoadDeps = async () => {
    const { jQueryLoaded, designDepsLoaded, loadJQuery, loadDesignDeps } = this.props
    try {
      if (!jQueryLoaded) {
        await loadJQuery()
      }
      if (!designDepsLoaded) {
        await loadDesignDeps()
      }
      FLOWABLE.setCommands()
    } catch (error) {
      message.error('依赖文件加载失败')
      this.setState({
        loading: false
      })
    }
  }

  initEditor = async () => {
    const { modelId } = this.props
    try {

      const lang = await loadLang()
      window.lang = lang

      const modelData = await queryEditorJson(modelId)
      const stencilData = await queryEditorStencil()

      const editorManager = new EditorManager(modelData)
      editorManager.setModelId(modelId);
      editorManager.setModelData(modelData);
      const baseUrl = "http://b3mn.org/stencilset/";
      editorManager.setStencilData(stencilData);
      const stencilSet = new ORYX.Core.StencilSet.StencilSet(baseUrl, stencilData);
      ORYX.Core.StencilSet.loadStencilSet(baseUrl, stencilSet, modelId);
      const pluginsData = await queryOryxPlugins()
      ORYX._loadPlugins(pluginsData);
      editorManager.bootEditor();

      this.setState({
        editorManager: editorManager,
        loading: false
      }, () => {
        this.initStencilData()
        this.bindEvents()
      })

    } catch (error) {
      this.setState({
        loading: false
      })
      message.error(error.message || '请求失败')
    }
  }

  initStencilData = () => {
    const { editorManager } = this.state
    var data = editorManager.getStencilData();
    var quickMenuDefinition = undefined;
    var ignoreForPaletteDefinition = undefined;

    if (data.namespace == 'http://b3mn.org/stencilset/cmmn1.1#') {
      quickMenuDefinition = [
        'HumanTask',
        'Association'
      ];
      ignoreForPaletteDefinition = [
        'CasePlanModel'
      ];
    } else {
      quickMenuDefinition = [
        'UserTask',
        'EndNoneEvent',
        'ExclusiveGateway',
        'CatchTimerEvent',
        'ThrowNoneEvent',
        'TextAnnotation',
        'SequenceFlow',
        'Association'
      ];
      ignoreForPaletteDefinition = [
        'SequenceFlow',
        'MessageFlow',
        'Association',
        'DataAssociation',
        'DataStore',
        'SendTask'
      ];
    }

    var stencilItemGroups = [];
    var quickMenuItems = [];
    var morphRoles = [];

    for (var i = 0; i < data.rules.morphingRules.length; i++) {
      var role = data.rules.morphingRules[i].role;
      var roleItem = {
        'role': role,
        'morphOptions': []
      };
      morphRoles.push(roleItem);
    }

    for (var stencilIndex = 0; stencilIndex < data.stencils.length; stencilIndex++) {

      // Check if the root group is the 'diagram' group. If so, this item should not be shown.
      var currentGroupName = data.stencils[stencilIndex].groups[0];
      if (['Diagram', 'BPMN.STENCILS.GROUPS.DIAGRAM', 'CMMN.STENCILS.GROUPS.DIAGRAM', 'DMN.STENCILS.GROUPS.DIAGRAM'].includes(currentGroupName)) {
        continue
      }

      var removed = false;
      if (data.stencils[stencilIndex].removed) {
        removed = true;
      }

      var currentGroup = undefined;

      if (!removed) {
        // Check if this group already exists. If not, we create a new one

        if (currentGroupName !== null && currentGroupName !== undefined && currentGroupName.length > 0) {

          currentGroup = findGroup(currentGroupName, stencilItemGroups); // Find group in root groups array
          if (currentGroup === null) {
            currentGroup = addGroup(currentGroupName, stencilItemGroups);
          }

          // Add all child groups (if any)
          for (var groupIndex = 1; groupIndex < data.stencils[stencilIndex].groups.length; groupIndex++) {
            var childGroupName = data.stencils[stencilIndex].groups[groupIndex];
            var childGroup = findGroup(childGroupName, currentGroup.groups);
            if (childGroup === null) {
              childGroup = addGroup(childGroupName, currentGroup.groups);
            }

            // The current group variable holds the parent of the next group (if any),
            // and is basically the last element in the array of groups defined in the stencil item
            currentGroup = childGroup;
          }

        }

      }

      // Construct the stencil item
      var stencilItem = {
        'id': data.stencils[stencilIndex].id,
        'name': data.stencils[stencilIndex].title,
        'description': data.stencils[stencilIndex].description,
        'icon': data.stencils[stencilIndex].icon,
        'type': data.stencils[stencilIndex].type,
        'roles': data.stencils[stencilIndex].roles,
        'removed': removed,
        'customIcon': false,
        'canConnect': false,
        'canConnectTo': false,
        'canConnectAssociation': false
      };

      if (data.stencils[stencilIndex].customIconId && data.stencils[stencilIndex].customIconId > 0) {
        stencilItem.customIcon = true;
        stencilItem.icon = data.stencils[stencilIndex].customIconId;
      }

      if (!removed) {
        if (quickMenuDefinition.indexOf(stencilItem.id) >= 0) {
          quickMenuItems[quickMenuDefinition.indexOf(stencilItem.id)] = stencilItem;
        }
      }

      if (stencilItem.id === 'TextAnnotation' || stencilItem.id === 'BoundaryCompensationEvent') {
        stencilItem.canConnectAssociation = true;
      }



      for (var i = 0; i < data.stencils[stencilIndex].roles.length; i++) {
        var stencilRole = data.stencils[stencilIndex].roles[i];

        if (data.namespace == 'http://b3mn.org/stencilset/cmmn1.1#') {
          if (stencilRole === 'association_start') {
            stencilItem.canConnect = true;
          } else if (stencilRole === 'association_end') {
            stencilItem.canConnectTo = true;
          }
        } else if (data.namespace == 'http://b3mn.org/stencilset/dmn1.2#') {
          if (stencilRole === 'information_requirement_start') {
            stencilItem.canConnect = true;
          } else if (stencilRole === 'information_requirement_end') {
            stencilItem.canConnectTo = true;
          }
        } else {
          if (stencilRole === 'sequence_start') {
            stencilItem.canConnect = true;
          } else if (stencilRole === 'sequence_end') {
            stencilItem.canConnectTo = true;
          }
        }

        for (var j = 0; j < morphRoles.length; j++) {
          if (stencilRole === morphRoles[j].role) {
            if (!removed) {
              morphRoles[j].morphOptions.push(stencilItem);
            }
            stencilItem.morphRole = morphRoles[j].role;
            break;
          }
        }
      }

      if (currentGroup) {
        // Add the stencil item to the correct group
        currentGroup.items.push(stencilItem);
        if (ignoreForPaletteDefinition.indexOf(stencilItem.id) < 0) {
          currentGroup.paletteItems.push(stencilItem);
        }
      } else {
        // It's a root stencil element
        if (!removed) {
          stencilItemGroups.push(stencilItem);
        }
      }
    }

    // 没有paletteItems的group不展示
    for (var i = 0; i < stencilItemGroups.length; i++) {
      if (stencilItemGroups[i].paletteItems && stencilItemGroups[i].paletteItems.length == 0) {
        stencilItemGroups[i].visible = false;
      }
    }

    this.setState({
      stencilItemGroups
    })

    var containmentRules = [];
    for (var i = 0; i < data.rules.containmentRules.length; i++) {
      var rule = data.rules.containmentRules[i];
      containmentRules.push(rule);
    }

    this.setState({
      containmentRules: containmentRules
    })

    // remove quick menu items which are not available anymore due to custom pallette
    var availableQuickMenuItems = [];
    for (var i = 0; i < quickMenuItems.length; i++) {
      if (quickMenuItems[i]) {
        availableQuickMenuItems[availableQuickMenuItems.length] = quickMenuItems[i];
      }
    }

    this.setState({
      quickMenuItems: availableQuickMenuItems,
      morphRoles
    })
  }

  bindEvents = () => {
    const { editorManager } = this.state
    var eventMappings = [
      {
        oryxType: ORYX.CONFIG.EVENT_SELECTION_CHANGED,
        flowableType: FLOWABLE.eventBus.EVENT_TYPE_SELECTION_CHANGE
      },
      {
        oryxType: ORYX.CONFIG.EVENT_DBLCLICK,
        flowableType: FLOWABLE.eventBus.EVENT_TYPE_DOUBLE_CLICK
      },
      {
        oryxType: ORYX.CONFIG.EVENT_MOUSEOUT,
        flowableType: FLOWABLE.eventBus.EVENT_TYPE_MOUSE_OUT
      },
      {
        oryxType: ORYX.CONFIG.EVENT_MOUSEOVER,
        flowableType: FLOWABLE.eventBus.EVENT_TYPE_MOUSE_OVER
      },
      {
        oryxType: ORYX.CONFIG.EVENT_EDITOR_INIT_COMPLETED,
        flowableType: FLOWABLE.eventBus.EVENT_TYPE_EDITOR_READY
      },
      {
        oryxType: ORYX.CONFIG.EVENT_PROPERTY_CHANGED,
        flowableType: FLOWABLE.eventBus.EVENT_TYPE_PROPERTY_VALUE_CHANGED
      }
    ];
    eventMappings.forEach(function (eventMapping) {
      editorManager.registerOnEvent(eventMapping.oryxType, function (event) {
        FLOWABLE.eventBus.dispatch(eventMapping.flowableType, event);
      });
    });

    // 绑定流程图中选中的节点发生变化事件
    editorManager.registerOnEvent(ORYX.CONFIG.EVENT_SELECTION_CHANGED, (event) => {
      let shapes = event.elements;
      this.setState({
        selectedShapes: shapes
      })
      if (shapes && shapes.length === 0) {
        shapes = [editorManager.getCanvas()];
      }
      this.setState({
        selectedShape: shapes.first()
      })
    })
  }

  handleChangeProperty = (values, shapeId) => {
    const { selectedShape, editorManager } = this.state
    var shape = selectedShape;
    var previousSelectedShape
    if (shapeId) {
      if (shape.id != shapeId && previousSelectedShape && previousSelectedShape.id == shapeId) {
        shape = previousSelectedShape;
      } else {
        shape = null;
      }
    }
    if (!shape) {
      return;
    }
    var key = Object.keys(values)[0];
    var newValue = values[key];
    var property = {
      key: key,
      value: newValue
    }
    // var oldValue = shape.properties.find(el => el.key === key).value;
    var oldValue = shape.properties.get(key);
    if (newValue != oldValue) {
      const command = new FLOWABLE.ChangePropertyCommand(key, oldValue, newValue, shape, editorManager.getEditor());
      editorManager.executeCommands([command]);
      editorManager.handleEvents({
        type: ORYX.CONFIG.EVENT_PROPWINDOW_PROP_CHANGED,
        elements: [shape],
        key: key
      });
      property.mode = 'read';
      const event = {
        type: FLOWABLE.eventBus.EVENT_TYPE_PROPERTY_VALUE_CHANGED,
        property: property,
        oldValue: oldValue,
        newValue: newValue
      };
      console.log(FLOWABLE.eventBus.EVENT_TYPE_PROPERTY_VALUE_CHANGED, 121212)
      FLOWABLE.eventBus.dispatch(event.type, event);
    } else {
      property.mode = 'read';
    }
  }

  handleQuickAction = (action) => {
    const { editorManager } = this.state
    if (action.type === 'create') {
      const { id: newItemId } = action.payload;
      var shapes = editorManager.getSelection();
      if (shapes && shapes.length == 1) {
        var currentSelectedShape = shapes.first();
        var containedStencil = undefined;
        var stencilSets = editorManager.getStencilSets().values();
        for (var i = 0; i < stencilSets.length; i++) {
          var stencilSet = stencilSets[i];
          var nodes = stencilSet.nodes();
          for (var j = 0; j < nodes.length; j++) {
            if (nodes[j].idWithoutNs() === newItemId) {
              containedStencil = nodes[j];
              break;
            }
          }
        }
        if (!containedStencil) return;
        var option = {
          type: currentSelectedShape.getStencil().namespace() + newItemId,
          namespace: currentSelectedShape.getStencil().namespace()
        };
        option['connectedShape'] = currentSelectedShape;
        option['parent'] = currentSelectedShape.parent;
        option['containedStencil'] = containedStencil;
        var args = {
          sourceShape: currentSelectedShape,
          targetStencil: containedStencil
        };
        var targetStencil = editorManager.getRules().connectMorph(args);
        // Check if there can be a target shape
        if (!targetStencil) return
        option['connectingType'] = targetStencil.id();
        const command = new FLOWABLE.CreateCommand(option, undefined, undefined, editorManager.getEditor());
        editorManager.executeCommands([command]);
      }
    } else if (action.type === 'delete') {
      this.toolbarRef.delete()
    }
  }

  // 父级组件通过实例获取流程数据方法
  getSubmitConfig = () => {
    const { editorManager } = this.state
    var modelMetaData = editorManager.getBaseModelData();
    var modelData = editorManager.getModel()

    return {
      modelMetaData,
      modelData,
      successCallBack: (params, responseData) => {
        // 保存完成后取消title上的星号
        editorManager.handleEvents({
          type: ORYX.CONFIG.EVENT_SAVED
        });

        // 解决不能连续两次保存的问题
        editorManager.setModelDataField('lastUpdated', responseData.lastUpdated)
        editorManager.setModelDataField('name', responseData.name)
        editorManager.setModelDataField('key', responseData.key)

        var saveEvent = {
          type: FLOWABLE.eventBus.EVENT_TYPE_MODEL_SAVED,
          model: params,
          modelId: modelMetaData.modelId,
          eventType: 'update-model'
        };

        FLOWABLE.eventBus.dispatch(FLOWABLE.eventBus.EVENT_TYPE_MODEL_SAVED, saveEvent);
      }
    }
  }

  findStencilItemInGroup = (stencilItemId, group) => {
    var item;
    // Check all items directly in this group
    for (var j = 0; j < group.items.length; j++) {
      item = group.items[j];
      if (item.id === stencilItemId) {
        return item;
      }
    }
    // Check the child groups
    if (group.groups && group.groups.length > 0) {
      for (var k = 0; k < group.groups.length; k++) {
        item = findStencilItemInGroup(stencilItemId, group.groups[k]);
        if (item) {
          return item;
        }
      }
    }
    return undefined;
  };

  getStencilItemById = (stencilItemId) => {
    const { stencilItemGroups } = this.state
    for (var i = 0; i < stencilItemGroups.length; i++) {
      var element = stencilItemGroups[i];
      // Real group
      if (element.items !== null && element.items !== undefined) {
        var item = this.findStencilItemInGroup(stencilItemId, element);
        if (item) {
          return item;
        }
      } else { // Root stencil item
        if (element.id === stencilItemId) {
          return element;
        }
      }
    }
    return undefined;
  };

  render() {
    const contextValue = {
      ...this.state,
      leftSlot: this.props.toolBarLeftSlot,
      rightSlot: this.props.toolBarRightSlot,
      getStencilItemById: this.getStencilItemById,
      handleQuickAction: this.handleQuickAction,
      handleChangeProperty: this.handleChangeProperty
    }
    return <DndProvider backend={HTML5Backend}>
      <ProcessEditorContext.Provider value={contextValue}>
        <Spin spinning={this.state.loading} wrapperClassName="p-d-wrap">
          <div className="p-d-head">
            <Toolbar ref={node => { this.toolbarRef = node }}></Toolbar>
          </div>
          <div className="p-d-body">
            <div className="p-d-left">
              <Palette></Palette>
            </div>
            <div className="p-d-middle">
              <DesignPanel></DesignPanel>
            </div>
            <div className="p-d-right">
              <Property></Property>
            </div>
          </div>
        </Spin>
      </ProcessEditorContext.Provider>
    </DndProvider>

  }
}

const mapStateToProps = (state) => {
  return {
    jQueryLoaded: state.flowable.jQueryLoaded,
    designDepsLoaded: state.flowable.designDepsLoaded
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    loadJQuery: () => {
      return dispatch(loadJQueryAction())
    },
    loadDesignDeps: () => {
      return dispatch(loadDesignDepsAction())
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true })(ProcessDesigner)